package sound;

import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.jdom.Document;
import org.jdom.Element;

import logic.nodes.lod.blocks.LeafBlock;
import main.InitGame;
import main.listeners.listenerClasses.GameListener;
import main.listeners.listenerClasses.SettingsListener;
import main.listeners.listenerClasses.gui.ChatActivatedEvent;
import main.listeners.listenerClasses.gui.ConsoleToggleEvent;
import main.listeners.listenerClasses.gui.GUIListener;
import main.listeners.listenerClasses.gui.IngameChangedEvent;
import main.listeners.listenerClasses.gui.ScoreBoardToggleEvent;
import main.listeners.listenerClasses.gui.SpawnMenuChangedEvent;

import paulscode.sound.Library;
import paulscode.sound.SoundSystem;
import paulscode.sound.SoundSystemConfig;
import paulscode.sound.codecs.CodecWav;
import paulscode.sound.libraries.LibraryJavaSound;
import paulscode.sound.libraries.LibraryLWJGLOpenAL;
import settings.Config;
import settings.SoundSettings;

import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.util.resource.ResourceLocatorTool;
import com.jmex.game.state.GameState;

import fileHandling.ModelImporter;
import fileHandling.SettingsLoader;

public class AudioManager extends GameState implements AbsAudioManager {
	
	public static final String AUDIO_DIR = "data/sounds/";
	public static final String EXPLOSION_01 = "explosion01.wav";
	public static final String SHOOT_01 = "shoot01.wav";
	public static final String COLLISION_02 = "collision02.wav";
	
	private static final String SOUND_CONFIG_FILE = "data/sounds/soundConfig.xml";
	private static final String VOLUME = "volume";
	private static final String LOOP = "loop";
	
	private static final int SFX_MODEL = SoundSystemConfig.ATTENUATION_LINEAR;
	
	private static AbsAudioManager manager;
	
	private SoundSystem system;
	
	private HashMap<String, Float> volumes;
	private HashSet<String> loopFiles;
	private HashMap<String, String> activeLoopSources;
	
	private ListenerPositionController lpController;
	private int sourceID;
	
	private Node rootNode;
	
	public AudioManager() {
		super();
		
		rootNode = new Node("Rootnode of Audiomanager");
		
		setActive(true);
		
		SoundSystemConfig.setLogger(new DummySoundLogger());
		
		Class<?> libraryType = Library.class;
		
		if(SoundSystem.libraryCompatible(LibraryLWJGLOpenAL.class)) 
			libraryType = LibraryLWJGLOpenAL.class;
		else 
		if(SoundSystem.libraryCompatible(LibraryJavaSound.class)) 
			libraryType = LibraryJavaSound.class;
		
		try {
			system = new SoundSystem(libraryType);
			SoundSystemConfig.setCodec("wav", CodecWav.class);
			
			activeLoopSources = new HashMap<String, String>();
			
			lpController = new ListenerPositionController(system);
			rootNode.addController(lpController);
			
			InitGame.get().addSettingsListener(new SettingsListener() {
				@Override
				public void soundSettingsChanged() {
					changeMasterVolume();
				}
				
				@Override
				public void graphicSettingsChanged() {}
				@Override
				public void controlSettingsChanged() {}
				@Override
				public void commonSettingsChanged() {}
			});
			
			initSoundConfig();
			changeMasterVolume();
			
			InitGame.get().addGUIListener(new GUIListener() {
				@Override
				public void ingameChanged(IngameChangedEvent event) {
					system.setMasterVolume(event.isIngame() ? SoundSettings.get().getMasterVolume() : 0f);
				}
				
				@Override
				public void spawnMenuChanged(SpawnMenuChangedEvent event) {}
				@Override
				public void scoreBoardToggled(ScoreBoardToggleEvent event) {}
				@Override
				public void consoleToggled(ConsoleToggleEvent event) {}
				@Override
				public void chatActivated(ChatActivatedEvent event) {}
			});
			
			InitGame.get().addGameListener(new GameListener() {
				@Override
				public void gameDestroyed() {
					Set<String> keys = activeLoopSources.keySet();
					for(String source : keys) {
						stopSound(source);
						system.removeSource(source);
					}
					
					system.removeTemporarySources();
				}
				
				@Override
				public void gameStarted() {}
				@Override
				public void gameFinished() {}
			});
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static AbsAudioManager get() { return manager; }
	
	public static void init(AbsAudioManager m) { manager = m; }
	
	@Override
	public void playSource(String source) {
		if(Config.get().soundEnabled() && !system.playing(source)) system.play(source);
	}
	
	@Override
	public String playSound(String file, Vector3f pos) {
		if(!Config.get().soundEnabled()) return null;
		String source = createSource(file, pos);
		system.play(source);
		
		return source;
	}
	
	@Override
	public void stopSound(String source) {
		if(system.playing(source)) system.stop(source);
	}
	
	@Override
	public void updateMovingNoise(String source, Vector3f pos) {
		system.setPosition(source, pos.x, pos.y, pos.z);
	}
	
	@Override
	public String createSource(String file, Vector3f pos) {
		String source = sourceID + "";
		sourceID++;
		boolean loop = loopFiles.contains(file);
		URL url = ModelImporter.getURL(AUDIO_DIR + file, ResourceLocatorTool.TYPE_AUDIO);
		
		system.newSource(false, source, url, file, loop, pos.x, pos.y, pos.z, SFX_MODEL, LeafBlock.particleDist);
		system.setVolume(source, getVolume(file) * system.getMasterVolume());
		system.setTemporary(source, !loop);
		
		if(loop) activeLoopSources.put(source, file);
		
		return source;
	}
	
	@Override
	public boolean isPlaying(String source) {
		return system.playing(source);
	}
	
	protected void changeMasterVolume() {
		float masterVol = SoundSettings.get().getMasterVolume();
		system.setMasterVolume(masterVol);
		
		Set<String> keys = activeLoopSources.keySet();
		for(String source : keys) {
			system.setVolume(source, getVolume(activeLoopSources.get(source)) * masterVol);
		}
	}
	
	private float getVolume(String file) {
		return volumes.get(file).floatValue();
	}
	
	@Override
	public void update(float tpf) {
		rootNode.updateGeometricState(tpf, true);
	}
	
	@Override
	public void render(float tpf) {}
	
	@Override
	public void cleanup() {
		if(system != null) system.cleanup();
	}
	
	private void initSoundConfig() {
		Document doc = SettingsLoader.getDocument(SOUND_CONFIG_FILE);
		Element root =  doc.getRootElement();
		
		volumes = new HashMap<String, Float>();
		loopFiles = new HashSet<String>();
		for(Object obj : root.getChildren()) {
			Element child = (Element)obj;
			String name = child.getName();
			
			if(name.endsWith(".wav")) {
				addChild(child, name);
			} else {
				addEngineChild(child, "start");
				addEngineChild(child, "main");
				addEngineChild(child, "end");
			}
			
		}
	}
	
	private void addEngineChild(Element parent, String partName) {
		addChild(parent.getChild(partName + ".wav"), parent.getName() + "/" + partName + ".wav");
	}
	
	private void addChild(Element child, String name) {
		String v = child.getAttributeValue(VOLUME);
		String l = child.getAttributeValue(LOOP);
		volumes.put(name, Float.valueOf(v));
		if(l.equals("true")) loopFiles.add(name);
	}
	
	@Override
	public void addController(Controller c) { rootNode.addController(c); }

	@Override
	public void removeController(Controller c) { rootNode.removeController(c); }
}